﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Cryptography.HashAlgorithms
{
    class MD4 : IHashAlgorithm
    {
        private UInt32[] state;
        private Byte[] buffer;
        private UInt32[] count;
        private UInt32[] x;

        private const Int32 S11 = 3;
        private const Int32 S12 = 7;
        private const Int32 S13 = 11;
        private const Int32 S14 = 19;
        private const Int32 S21 = 3;
        private const Int32 S22 = 5;
        private const Int32 S23 = 9;
        private const Int32 S24 = 13;
        private const Int32 S31 = 3;
        private const Int32 S32 = 9;
        private const Int32 S33 = 11;
        private const Int32 S34 = 15;

        private byte[] digest;

        public MD4()
        {
            this.state = new UInt32[4];
            this.count = new UInt32[2];
            this.buffer = new Byte[64];
            this.digest = new Byte[16];
            this.x = new UInt32[16];
            Initialize();
        }

        public void Initialize()
        {
            this.count[0] = 0;
            this.count[1] = 0;
            this.state[0] = 0x67452301;                         //word A: 01 23 45 67
            this.state[1] = 0xefcdab89;                         //word B: 89 ab cd ef
            this.state[2] = 0x98badcfe;                         //word C: fe dc ba 98
            this.state[3] = 0x10325476;                         //word D: 76 54 32 10
            Array.Clear(this.buffer, 0, this.buffer.Length);
            Array.Clear(this.x, 0, this.x.Length);
        }


        #region MainOperations
        private void HashCore(Byte[] array, int ibStart, int cbSize)
        {
            int index = (int)((count[0] >> 3) & 0x3F);
            count[0] += (uint)(cbSize << 3);
            if (count[0] < (cbSize << 3))
                count[1]++;
            count[1] += (uint)(cbSize >> 29);

            int partLen = 64 - index;
            int i = 0;
            if (cbSize >= partLen)
            {
                Buffer.BlockCopy(array, ibStart, buffer, index, partLen);
                MD4Transform(state, buffer, 0);

                for (i = partLen; i + 63 < cbSize; i += 64)
                    MD4Transform(state, array, i);
                index = 0;
            }
            Buffer.BlockCopy(array, ibStart + i, buffer, index, (cbSize - i));
        }

        private Byte[] HashFinal()
        {
            byte[] bits = new byte[8];
            Encode(bits, count);

            uint index = ((count[0] >> 3) & 0x3f);
            int padLen = (int)((index < 56) ? (56 - index) : (120 - index));
            HashCore(Padding(padLen), 0, padLen);

            HashCore(bits, 0, 8);
            Encode(digest, state);
            Initialize();
            return digest;
        }

        #endregion

        #region Operations

        /// <summary>
        /// 
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        private Byte[] Padding(int length)
        {
            if (length > 0)
            {
                Byte[] padding = new Byte[length];
                padding[0] = 0x80;
                return padding;
            }
            else
                return null;
        }

        private UInt32 F(UInt32 x, UInt32 y, UInt32 z)
        {
            return (UInt32)(((x) & (y)) | ((~x) & (z)));
        }

        private UInt32 G(UInt32 x, UInt32 y, UInt32 z)
        {
            return (UInt32)(((x) & (y)) | ((x) & (z)) | ((y) & (z)));
        }

        private UInt32 H(UInt32 x, UInt32 y, UInt32 z)
        {
            return (UInt32)((x) ^ (y) ^ (z));
        }

        private UInt32 RotateLeft(UInt32 x, Byte n)
        {
            return (UInt32)(((x) << (n)) | ((x) >> (32 - (n))));
        }

        private void FF(ref UInt32 a, UInt32 b, UInt32 c, UInt32 d, UInt32 x, Byte s)
        {
            a += F(b, c, d) + x;
            a = RotateLeft(a, s);
        }

        private void GG(ref UInt32 a, UInt32 b, UInt32 c, UInt32 d, UInt32 x, Byte s)
        {
            a += G(b, c, d) + x + 0x5a827999;
            a = RotateLeft(a, s);
        }

        private void HH(ref UInt32 a, UInt32 b, UInt32 c, UInt32 d, UInt32 x, Byte s)
        {
            a += H(b, c, d) + x + 0x6ed9eba1;
            a = RotateLeft(a, s);
        }

        private void Encode(Byte[] output, UInt32[] input)
        {
            for (int i = 0, j = 0; j < output.Length; i++, j += 4)
            {
                output[j] = (Byte)(input[i]);
                output[j+1] = (Byte)(input[i] >> 8);
                output[j+2] = (Byte)(input[i] >> 16);
                output[j+3] = (Byte)(input[i] >> 24);
            }
        }

        private void Decode(UInt32[] output, Byte[] input, int index)
        {
            for (int i = 0, j = index; i < output.Length; i++, j += 4)
                output[i] = (UInt32)((input[j] | (input[j + 1]) << 8) | (input[j + 2] << 16) | (input[j + 3] << 24));
        }

        private void MD4Transform(UInt32[] state, Byte[] block, int index)
        {
            UInt32 a = state[0];
            UInt32 b = state[1];
            UInt32 c = state[2];
            UInt32 d = state[3];

            this.Decode(x, block, index);

            /* Round 1 */
            FF(ref a, b, c, d, x[0], S11); /* 1 */
            FF(ref d, a, b, c, x[1], S12); /* 2 */
            FF(ref c, d, a, b, x[2], S13); /* 3 */
            FF(ref b, c, d, a, x[3], S14); /* 4 */
            FF(ref a, b, c, d, x[4], S11); /* 5 */
            FF(ref d, a, b, c, x[5], S12); /* 6 */
            FF(ref c, d, a, b, x[6], S13); /* 7 */
            FF(ref b, c, d, a, x[7], S14); /* 8 */
            FF(ref a, b, c, d, x[8], S11); /* 9 */
            FF(ref d, a, b, c, x[9], S12); /* 10 */
            FF(ref c, d, a, b, x[10], S13); /* 11 */
            FF(ref b, c, d, a, x[11], S14); /* 12 */
            FF(ref a, b, c, d, x[12], S11); /* 13 */
            FF(ref d, a, b, c, x[13], S12); /* 14 */
            FF(ref c, d, a, b, x[14], S13); /* 15 */
            FF(ref b, c, d, a, x[15], S14); /* 16 */

            /* Round 2 */
            GG(ref a, b, c, d, x[0], S21); /* 17 */
            GG(ref d, a, b, c, x[4], S22); /* 18 */
            GG(ref c, d, a, b, x[8], S23); /* 19 */
            GG(ref b, c, d, a, x[12], S24); /* 20 */
            GG(ref a, b, c, d, x[1], S21); /* 21 */
            GG(ref d, a, b, c, x[5], S22); /* 22 */
            GG(ref c, d, a, b, x[9], S23); /* 23 */
            GG(ref b, c, d, a, x[13], S24); /* 24 */
            GG(ref a, b, c, d, x[2], S21); /* 25 */
            GG(ref d, a, b, c, x[6], S22); /* 26 */
            GG(ref c, d, a, b, x[10], S23); /* 27 */
            GG(ref b, c, d, a, x[14], S24); /* 28 */
            GG(ref a, b, c, d, x[3], S21); /* 29 */
            GG(ref d, a, b, c, x[7], S22); /* 30 */
            GG(ref c, d, a, b, x[11], S23); /* 31 */
            GG(ref b, c, d, a, x[15], S24); /* 32 */

            HH(ref a, b, c, d, x[0], S31); /* 33 */
            HH(ref d, a, b, c, x[8], S32); /* 34 */
            HH(ref c, d, a, b, x[4], S33); /* 35 */
            HH(ref b, c, d, a, x[12], S34); /* 36 */
            HH(ref a, b, c, d, x[2], S31); /* 37 */
            HH(ref d, a, b, c, x[10], S32); /* 38 */
            HH(ref c, d, a, b, x[6], S33); /* 39 */
            HH(ref b, c, d, a, x[14], S34); /* 40 */
            HH(ref a, b, c, d, x[1], S31); /* 41 */
            HH(ref d, a, b, c, x[9], S32); /* 42 */
            HH(ref c, d, a, b, x[5], S33); /* 43 */
            HH(ref b, c, d, a, x[13], S34); /* 44 */
            HH(ref a, b, c, d, x[3], S31); /* 45 */
            HH(ref d, a, b, c, x[11], S32); /* 46 */
            HH(ref c, d, a, b, x[7], S33); /* 47 */
            HH(ref b, c, d, a, x[15], S34); /* 48 */

            state[0] += a;
            state[1] += b;
            state[2] += c;
            state[3] += d;

        }

        #endregion

        #region IHashAlgorithm Members

        public string Encrypt(string message)
        {
            System.Text.ASCIIEncoding encoding = new ASCIIEncoding();
            Byte[] inputBytes = encoding.GetBytes(message);
            this.HashCore(inputBytes, 0, inputBytes.Length);
            Byte[] digest = this.HashFinal();
            StringBuilder final = new StringBuilder();
            for (int i = 0; i < 16; i++)
                final.Append(String.Format("{0:x2}", digest[i])); 
            return final.ToString();
        }

        #endregion
    }
}
